// 因同一文件夹下无法存在两个main函数，所以将客户端代码提取到外部进行编写

//main.go

package main

import (
	"bufio"
	"context"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client"

	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"

	ma "github.com/multiformats/go-multiaddr"
)

func main() {
	dist := flag.String("d", "", "客户端的设备ID")
	reid := flag.String("reid", "", "中转服务器的ID")
	read := flag.String("read", "", "中转服务器的Addr地址")
	flag.Parse()
	run(*dist, *reid, *read)
}

func run(dist, reid, read string) {
	unreachableNode, err := libp2p.New(
		libp2p.NoListenAddrs,
		// 通常不需要EnableRelay（），因为它是默认启用的，但NoListAdddrs会覆盖它，所以我们将再次显式添加它。
		libp2p.EnableRelay(),
	)
	if err != nil {
		log.Printf("p2p通道创建失败: %v", err)
		return
	}

	node := fmt.Sprintf("%s/p2p/%s", read, reid)
	log.Println("开始连接中继节点：" + node)
	ms1, err := ma.NewMultiaddr(node)
	if err != nil {
		log.Printf("中转服务器信息解析失败: %v", err)
		return
	}
	relay1info, err := peer.AddrInfoFromP2pAddr(ms1)
	if err != nil {
		log.Printf("将P2P地址转换成P2P地址列表失败: %v", err)
		return
	}

	if err := unreachableNode.Connect(context.Background(), *relay1info); err != nil {
		log.Printf("中转服务器连接失败: %v", err)
		return
	}
	_, err = client.Reserve(context.Background(), unreachableNode, *relay1info)
	if err != nil {
		log.Printf("设置中继服务器预留信息失败 %v", err)
		return
	}

	unreachableNode.SetStreamHandler("/chatStream", func(s network.Stream) {
		log.Println("中继服务器已打通，正在监听流：")

		rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

		go readData(rw)
		go writeData(rw)
	})

	if dist == "" {
		//说明本节点是等待被另一个节点call的，只需要与中转节点连接一下注册到即可
		//写一个简单的阻塞
		fmt.Printf("初始客户端已就位，请运行连接客户端\n")
		fmt.Printf("在其他客户端运行： go run ./main.go -read %s -reid %s -d %v\n", read, reid, unreachableNode.ID())
		select {}
	}

	node_p1 := "/p2p/" + relay1info.ID.String() + "/p2p-circuit/p2p/" + dist
	log.Println("开始连接节点：" + node_p1)
	relayaddr, err := ma.NewMultiaddr(node_p1)
	if err != nil {
		log.Println("客户端节点连接失败：", err)
		return
	}

	// 客户端地址转换
	useRelayToDistInfo, err := peer.AddrInfoFromP2pAddr(relayaddr)
	if err != nil {
		log.Println("客户端节点地址转换为多节点地址失败", err)
		return
	}
	if err := unreachableNode.Connect(context.Background(), *useRelayToDistInfo); err != nil {
		log.Printf("客户端节点无法连接: %v", err)
		return
	}

	log.Println("客户端节点连接成功")

	// 打开读写流
	s, err := unreachableNode.NewStream(network.WithUseTransient(context.Background(), "chatStream"), useRelayToDistInfo.ID, "/chatStream")
	if err != nil {
		log.Println("新的流打开失败: ", err)
		return
	}

	rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

	go writeData(rw)
	go readData(rw)

	select {}
}

func readData(rw *bufio.ReadWriter) {
	for {
		str, _ := rw.ReadString('\n')

		if str == "" {
			return
		}
		if str != "\n" {
			// Green console colour: 	\x1b[32m
			// Reset console colour: 	\x1b[0m
			fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
		}

	}
}

func writeData(rw *bufio.ReadWriter) {
	stdReader := bufio.NewReader(os.Stdin)

	for {
		fmt.Print("> ")
		sendData, err := stdReader.ReadString('\n')
		if err != nil {
			log.Println(err)
			return
		}

		rw.WriteString(fmt.Sprintf("%s\n", sendData))
		rw.Flush()
	}
}
